// Copyright (c) 2011 SameGoal LLC.
// All Rights Reserved.
// Author: Andy Hochhaus <ahochhaus@samegoal.com>

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "net/http/server/comet/timer_wrapper.h"

#include <glog/logging.h>

#include <map>
#include <string>

#include "base/init.h"
#include "net/http/server/comet/session.h"

namespace sg {
namespace net {
namespace http {
namespace server {
namespace comet {

struct timeval TimerWrapper::secs2_ = { 2, 0 };
struct timeval TimerWrapper::secs30_ = { 30, 0 };
struct event_base *TimerWrapper::base_ = NULL;

TimerWrapper::TimerWrapper(const uint64 &seconds, std::function<void()> cb)
    : cb_(cb), ev_(NULL) {
  Reset(seconds);
}

TimerWrapper::~TimerWrapper() {
  Stop();
}

void TimerWrapper::Init(struct event_base *base) {
  base_ = base;

  // Use O(1) timers for common timeout values. See:
  // "Optimizing common timeouts" in
  // http://www.wangafu.net/~nickm/libevent-book/Ref4_event.html
  struct timeval tv2_in = { 2, 0 };
  const struct timeval *tv2_out;
  tv2_out = event_base_init_common_timeout(base, &tv2_in);
  memcpy(&secs2_, tv2_out, sizeof(struct timeval));

  struct timeval tv30_in = { 30, 0 };
  const struct timeval *tv30_out;
  tv30_out = event_base_init_common_timeout(base, &tv30_in);
  memcpy(&secs30_, tv30_out, sizeof(struct timeval));
}

void TimerWrapper::Reset(const uint64 &seconds) {
  CHECK_NOTNULL(base_);
  Stop();

  ev_ = evtimer_new(base_, &TimerWrapper::TimerCallbackInternal, this);
  if (seconds == 2) {
    evtimer_add(ev_, &secs2_);
  } else if (seconds == 30) {
    evtimer_add(ev_, &secs30_);
  } else {
    struct timeval tv;
    tv.tv_usec = 0;
    tv.tv_sec = seconds;
    evtimer_add(ev_, &tv);
  }
}

void TimerWrapper::Stop() {
  if (ev_ != NULL) {
    evtimer_del(ev_);
  }

  ev_ = NULL;
}

void TimerWrapper::TimerCallbackInternal(
    int /* fd */, int16_t /* event */, void *arg) {
  TimerWrapper *timer_wrapper = static_cast<TimerWrapper *>(arg);

  timer_wrapper->ev_ = NULL;
  timer_wrapper->cb_();
}

}  // namespace comet
}  // namespace server
}  // namespace http
}  // namespace net
}  // namespace sg
